using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Common;
using System.Text.RegularExpressions;
using System.Xml;
using Sinacor.Infra.Service.Data;
using Sinacor.Infra.Common.Caching;
using Sinacor.Infra.Service.Security.Authorization;
using Sinacor.Infra.Common.Exceptions;
using Sinacor.Infra.Common.Validation;
using System.Linq;

namespace Sinacor.Infra.Service.ExceptionHandling
{
    public class ErrorMessage
    {
        public static void ConfigureExceptionMessage(Exception exception)
        {
            try
            {
                string errorMessage;
                string resourceKey;

                if (exception is ValidationException)
                {
                    foreach (ValidationError validationError in ((ValidationException)exception).ValidationErrors.Values)
                    {
                        errorMessage = ErrorMessage.RetrieveErrorMessage(validationError.ErrorNumber, out resourceKey, validationError.Parameters.ToArray());

                        validationError.ConfigureMessage(errorMessage);
                        validationError.ResourceKey = resourceKey;
                    }

                    ((ValidationException)exception).ConfigureMessage();
                }
                else if (exception is BaseErrorNumberException)
                {
                    object[] parameters = new object[0];

                    if (exception is BusinessException)
                        parameters = ((BusinessException)exception).Parameters.ToArray();

                    errorMessage = ErrorMessage.RetrieveErrorMessage(((BaseErrorNumberException)exception).ErrorNumber, out resourceKey, parameters);

                    ((BaseErrorNumberException)exception).ConfigureMessage(errorMessage);
                    ((BaseErrorNumberException)exception).ResourceKey = resourceKey;
                }
            }
            catch (Exception ex)
            {
                if (exception != null && exception.Data != null)
                    exception.Data.Add("ConfigureExceptionMessageError", ex.ToString());
            }
        }

        /// <summary>
        /// Retrieves from Infra database the business error message associated to error number.
        /// </summary>
        /// <param name="errorNumber">The error number that originated the Exception.</param>
        /// <param name="parameters">Input paramaters to be used in message formatting.</param>
        /// <returns>The formatted message to be associated with this exception.</returns>
        public static string RetrieveErrorMessage(int errorNumber, out string resourceKey, params object[] parameters)
        {
            const string errorMessageCacheKeyPrefix = "Sinacor.Infra.Service.ExceptionHandling.ErrorMessage.";
            const string resourceKeyCacheKeyPrefix = "Sinacor.Infra.Service.ExceptionHandling.ResourceKey.";
            const int cacheTimeoutInMinutes = 5;
            resourceKey = "";

            //string query = "SELECT MENSAGEMEXCECAO FROM TFWEXCECAO WHERE CODIGOEXCECAO= " + errorNumber.ToString();
            string query = "SELECT E.MENSAGEMEXCECAO, M.RESOURCEKEY " +
                           "FROM TFWEXCECAO E " +
                           "LEFT JOIN TFWMENSAGEM M ON E.CODIGOMENSAGEM = M.CODIGOMENSAGEM " +
                           "WHERE CODIGOEXCECAO = :errorNumber";

            Cache cache = new Cache();

            string errorMessageCacheKey = errorMessageCacheKeyPrefix + errorNumber.ToString();
            string resourceKeyCacheKey = resourceKeyCacheKeyPrefix + errorNumber.ToString();
            string errorMessage = cache[errorMessageCacheKey] as string;
            resourceKey = cache[resourceKeyCacheKey] as string;

            string formattedErrorMessage = "";

            // Replaces the %0, %1 by the values {0}, {1} and so on.
            // The code is replacing values varying to 3 digits, that is %999.
            Regex replaceSearch = new Regex(@"[\%](?<numero>[0-9]{1,3})");

            if (errorMessage == null)
            {
                using (OracleDataBase db = new OracleDataBase())
                {
                    using (DbCommand command = db.GetSqlStringCommand(query))
                    {
                        db.AddInParameter(command, "errorNumber", DbType.Int32, errorNumber);

                        using (IDataReader reader = db.ExecuteReader(command))
                        {
                            if (reader.Read())
                            {
                                errorMessage = reader.GetValue<string>("MENSAGEMEXCECAO");
                                resourceKey = reader.GetValue<string>("RESOURCEKEY");
                            }
                            else
                                errorMessage = string.Format("Error number '{0}' not found", errorNumber);
                        }
                    }
                }
                Cache.Add(errorMessageCacheKey, errorMessage, new TimeSpan(0, cacheTimeoutInMinutes, 0));
                Cache.Add(resourceKeyCacheKey, resourceKey, new TimeSpan(0, cacheTimeoutInMinutes, 0));
            }

            formattedErrorMessage = errorMessage;

            // Validates the parameters for message formatting.
            if (parameters.Length > 0)
            {
                string evaluator = "{${numero}}";
                formattedErrorMessage = replaceSearch.Replace(errorMessage, evaluator);

                // The number of parameters of the error message must be exact the same of parameters
                // number received. Otherwise, the Format method returns an error.
                try
                {
                    formattedErrorMessage = string.Format(formattedErrorMessage, parameters);
                }
                catch (FormatException)
                {
                    // The message has a wrong format in database or the number 
                    // of parameters is inconsistent.
                    formattedErrorMessage = errorMessage;
                }
            }

            return formattedErrorMessage;
        }

        /// <summary>
        /// Convert XML Arguments to Parameters.
        /// </summary>
        /// <param name="xmlArgs">XML Arguments.</param>
        /// <returns>Output paramaters to be used in message formatting.</returns>
        public static List<object> ConvertXmlArgumentsToParameters(string xmlArgs)
        {

            try
            {
                XmlDocument xmlDoc = new XmlDocument();

                xmlDoc.LoadXml(xmlArgs);

                XmlNodeList xmlNodes = xmlDoc.SelectNodes("//Arguments/Argument");

                if (xmlNodes.Count == 0)
                    throw new Exception("Node '//Arguments/Argument' not found. XML: " + xmlArgs);

                List<Object> _parameters = new List<Object>();

                foreach (System.Xml.XmlNode _xmlNode in xmlNodes)
                {
                    XmlNode xmlType = null;
                    XmlNode xmlValue = null;

                    xmlType = _xmlNode.Attributes.GetNamedItem("type");
                    if (xmlType == null)
                        throw new Exception("Attribute 'type' not found. XML: " + xmlArgs);

                    xmlValue = _xmlNode.Attributes.GetNamedItem("value");
                    if (xmlValue == null)
                        throw new Exception("Attribute 'value' not found. XML: " + xmlArgs);

                    _parameters.Add(DinamicType(xmlType.Value.ToUpper(), xmlValue.Value));
                }

                return _parameters;
            }
            catch (Exception ex)
            {
                throw new Exception("Error converting XML arguments to parameters", ex);
            }

        }

        private static object DinamicType(string tipo, string valor)
        {
            try
            {
                object retorno;

                switch (tipo.ToUpper())
                {
                    case "VARCHAR":
                        retorno = Convert.ToString(valor);
                        break;
                    case "VARCHAR2":
                        retorno = Convert.ToString(valor);
                        break;
                    case "NUMBER":
                        retorno = Convert.ToInt32(valor);
                        break;
                    case "FLOAT":
                        retorno = Convert.ToDecimal(valor);
                        break;
                    case "DOUBLE":
                        retorno = Convert.ToDecimal(valor);
                        break;
                    case "DATE":
                        retorno = new DateTime(Convert.ToInt32(valor.Substring(6, 4)), Convert.ToInt32(valor.Substring(3, 2)), Convert.ToInt32(valor.Substring(0, 2)));
                        break;
                    case "DATETIME":
                        retorno = new DateTime(Convert.ToInt32(valor.Substring(6, 4)), Convert.ToInt32(valor.Substring(3, 2)), Convert.ToInt32(valor.Substring(0, 2)));
                        break;
                    case "TIMESTAMP":
                        retorno = new DateTime(Convert.ToInt32(valor.Substring(6, 4)), Convert.ToInt32(valor.Substring(3, 2)), Convert.ToInt32(valor.Substring(0, 2)));
                        break;
                    default:
                        retorno = string.Empty;
                        break;
                }
                return retorno;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + " (type: " + tipo + " value: " + valor + ")");
            }
        }


    }
}
